package org.example.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.example.admin.common.biz.user.UserContext;
import org.example.admin.common.convention.exception.ClientException;
import org.example.admin.common.convention.result.Result;
import org.example.admin.common.toolkit.RandomStringGenerator;
import org.example.admin.dao.entity.GroupDO;
import org.example.admin.dao.mapper.GroupMapper;
import org.example.admin.dto.req.GroupSortReqDTO;
import org.example.admin.dto.req.GroupUpdateReqDTO;
import org.example.admin.dto.resp.GroupBaseDTO;
import org.example.admin.remote.ShortLinkRemoteService;
import org.example.admin.remote.dto.resp.GroupShortLinkCountRespDTO;
import org.example.admin.service.GroupService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.sql.rowset.serial.SerialException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.example.admin.common.constant.RedisCacheConstant.LOCK_GROUP_CREAT;
import static org.example.admin.common.enums.errorEnum.GroupErrorCodeEnum.GROUP_DELETE_ERROR;
import static org.example.admin.common.enums.errorEnum.GroupErrorCodeEnum.GROUP_UPDATE_ERROR;

/**
 * @author weibin
 * @since 2025/10/4 18:29
 */
@Service
@RequiredArgsConstructor
public class GroupServiceImpl extends ServiceImpl<GroupMapper, GroupDO> implements GroupService {

    private final ShortLinkRemoteService shortLinkRemoteService = new ShortLinkRemoteService() {};
    private final RedissonClient redissonClient;

    @Value("${short-link.maxGroupNum}")
    private Integer maxGroupNum;

    @Override
    public void saveGroup(String groupName) {
        this.saveGroup(UserContext.getUsername(), groupName);
    }

    @SneakyThrows
    @Override
    public void saveGroup(String username, String groupName) {

        /*
        约束一个用户创建的分组数量是有限个
        此外，由于一个用户可能多端登录，需要考虑他们同时创建分组的情况，所以使用分布式锁来限制一下
         */
        RLock lock = redissonClient.getLock(String.format(LOCK_GROUP_CREAT, username));
        lock.lock();
        try {

            // 检查分组数量是否已达约束
            LambdaQueryWrapper<GroupDO> groupQueryWrapper = Wrappers.lambdaQuery(GroupDO.class)
                    .eq(GroupDO::getUsername, username)
                    .eq(GroupDO::getDelFlag, 0);
            List<GroupDO> groupDOs = baseMapper.selectList(groupQueryWrapper);
            if (CollUtil.isNotEmpty(groupDOs) && groupDOs.size() == maxGroupNum) {
                throw new ClientException("分组数量不能超过" + maxGroupNum);
            }

            /*
            不用担心这里经常会查库，重复的概率很小
             */
            // 生成5为分组唯一标识
            String gid;
            GroupDO selectByGid;
            LambdaQueryWrapper<GroupDO> queryWrapper;
            do {
                gid = RandomStringGenerator.generate();
                queryWrapper = Wrappers.lambdaQuery(GroupDO.class)
                        .eq(GroupDO::getDelFlag, 0)
                        .eq(GroupDO::getGid, gid);
                selectByGid = baseMapper.selectOne(queryWrapper);
            } while (selectByGid != null);

            GroupDO groupDO = GroupDO.builder()
                    .name(groupName)
                    .gid(gid)
                    .username(Optional.ofNullable(username).orElse(UserContext.getUsername()))
                    .sortOrder(0)
                    .build();
            baseMapper.insert(groupDO);
        } catch (Exception ex) {
            throw new SerialException("创建分组失败：" + ex.getMessage());
        } finally {
            lock.unlock();
        }
    }

    @Override
    public List<GroupBaseDTO> listGroup() {
        LambdaQueryWrapper<GroupDO> queryWrapper = Wrappers.lambdaQuery(GroupDO.class)
                .eq(GroupDO::getUsername, UserContext.getUsername())
                .eq(GroupDO::getDelFlag, 0)
                .orderByDesc(GroupDO::getSortOrder)
                .orderByDesc(GroupDO::getUpdateTime);
        List<GroupDO> groupDOs = baseMapper.selectList(queryWrapper);

        // 补充分组内短链接数量
        Result<List<GroupShortLinkCountRespDTO>> listResult = shortLinkRemoteService.listGroupShortLinkCount(groupDOs.stream().map(GroupDO::getGid).toList());
        Map<String, Long> gidAndCountMap = listResult.getData().stream().collect(Collectors.toMap(GroupShortLinkCountRespDTO::getGid, GroupShortLinkCountRespDTO::getShortLinkCount));

        List<GroupBaseDTO> groupBaseDTOs = BeanUtil.copyToList(groupDOs, GroupBaseDTO.class);
        groupBaseDTOs.forEach(x -> x.setShortLinkCount(gidAndCountMap.getOrDefault(x.getGid(), 0L)));
        return groupBaseDTOs;
    }

    @Override
    public void updateGroup(GroupUpdateReqDTO requestParam) {
        LambdaUpdateWrapper<GroupDO> updateWrapper = Wrappers.lambdaUpdate(GroupDO.class)
                .set(GroupDO::getName, requestParam.getName())
                .eq(GroupDO::getDelFlag, 0)
                .eq(GroupDO::getUsername, UserContext.getUsername())
                .eq(GroupDO::getGid, requestParam.getGid());
        boolean update = this.update(updateWrapper);
        if (!update) {
            throw new ClientException(GROUP_UPDATE_ERROR);
        }
    }

    @Override
    public void deleteGroup(String gid) {
        LambdaUpdateWrapper<GroupDO> updateWrapper = Wrappers.lambdaUpdate(GroupDO.class)
                .set(GroupDO::getDelFlag, 1)
                .eq(GroupDO::getUsername, UserContext.getUsername())
                .eq(GroupDO::getGid, gid);
        boolean update = this.update(updateWrapper);
        if (!update) {
            throw new ClientException(GROUP_DELETE_ERROR);
        }
    }

    @Override
    public void sortGroup(List<GroupSortReqDTO> requestParam) {
        requestParam.forEach(x -> {
                    this.lambdaUpdate()
                            .set(GroupDO::getSortOrder, x.getSortOrder())
                            .eq(GroupDO::getGid, x.getGid())
                            .eq(GroupDO::getDelFlag, 0)
                            .update();
                }
        );

    }

}
